package service

import (
	"context"
	"database/sql"

	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/samber/lo"
)

type AccountGroupUserService interface {
	QueryCreatorID(ctx context.Context, accountGroupID string) (string, error)
	QueryIDByCreator(ctx context.Context, userID string) (string, error)
	QueryAccountGroupOwner(ctx context.Context, accountGroupID string) (*repository.AccountGroupOwner, error)
	QueryAvailableAccountGroups(ctx context.Context, userID string) ([]dto.AccountGroupResponse, error)
	Count(ctx context.Context, accountGroupID string) (int64, error)
	ExistByAccountGroupAndUser(ctx context.Context, accountGroupID string, userID string) (bool, error)
	ExistByAccountGroupAndUserAndIsAccountGroupCreator(ctx context.Context, accountGroupID string, userID string) (bool, error)
	Remove(ctx context.Context, accountGroupID string, userID string, username string) error
	InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.AccountGroupUser) error
	DeleteByAccountGroupAndUserWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error
}

// NewAccountGroupUserService creates a new instance of AccountGroupUserService.
func NewAccountGroupUserService(db *sql.DB) AccountGroupUserService {
	return &accountGroupUserService{
		db:                         db,
		accountGroupUserRepository: repository.NewAccountGroupUserRepository(db),
	}
}

// QueryCreatorID queries creator id by account group id.
func (s *accountGroupUserService) QueryCreatorID(ctx context.Context, accountGroupID string) (string, error) {
	return s.accountGroupUserRepository.QueryCreatorID(ctx, accountGroupID)
}

// QueryIDByCreator queries user's own account group id.
func (s *accountGroupUserService) QueryIDByCreator(ctx context.Context, userID string) (string, error) {
	return s.accountGroupUserRepository.QueryIDByCreator(ctx, userID)
}

func (s *accountGroupUserService) QueryAccountGroupOwner(ctx context.Context, accountGroupID string) (*repository.AccountGroupOwner, error) {
	return s.accountGroupUserRepository.QueryAccountGroupOwner(ctx, accountGroupID)
}

// QueryAvailableAccountGroups queries available account groups for user.
func (s *accountGroupUserService) QueryAvailableAccountGroups(ctx context.Context, userID string) ([]dto.AccountGroupResponse, error) {
	accountGroupIDs, err := s.accountGroupUserRepository.QueryIDs(ctx, userID)

	if err != nil {
		return nil, err
	}

	creators, err := s.accountGroupUserRepository.QueryCreators(ctx, accountGroupIDs)

	if err != nil {
		return nil, err
	}

	result := lo.Map(creators, func(item repository.AccountGroupUser, _ int) dto.AccountGroupResponse {
		return dto.AccountGroupResponse{
			ID:             item.AccountGroupID,
			IsOwner:        item.ID == userID,
			OwnerID:        item.ID,
			OwnerUsername:  item.Username,
			OwnerFullName:  item.FullName,
			OwnerAvatarUrl: NewUserService(nil).GetAvatarUrl(&item.AvatarFilename),
		}
	})

	return result, nil
}

// Count counts the account group user by accountGroupID.
func (s *accountGroupUserService) Count(ctx context.Context, accountGroupID string) (int64, error) {
	return s.accountGroupUserRepository.Count(ctx, accountGroupID)
}

// ExistByAccountGroupAndUser checks if user exists in account group.
func (s *accountGroupUserService) ExistByAccountGroupAndUser(ctx context.Context, accountGroupID string, userID string) (bool, error) {
	return s.accountGroupUserRepository.ExistByAccountGroupAndUser(ctx, accountGroupID, userID)
}

// ExistByAccountGroupAndUserAndIsAccountGroupCreator checks if user exists in account group and is account group creator.
func (s *accountGroupUserService) ExistByAccountGroupAndUserAndIsAccountGroupCreator(ctx context.Context, accountGroupID string, userID string) (bool, error) {
	return s.accountGroupUserRepository.ExistByAccountGroupAndUserAndIsAccountGroupCreator(ctx, accountGroupID, userID)
}

// Remove removes user from account group.
func (s *accountGroupUserService) Remove(ctx context.Context, accountGroupID string, userID string, username string) error {
	tx, err := s.db.Begin()

	if err != nil {
		return err
	}

	defer tx.Rollback()

	// Removes user from this account group.
	if err = s.accountGroupUserRepository.DeleteNonCreatorWithTx(ctx, tx, accountGroupID, userID); err != nil {
		return err
	}

	// Removes user roles in this account group.
	if err = NewUserRoleService(s.db).DeleteInAndWithTx(ctx, tx, accountGroupID, userID); err != nil {
		return err
	}

	userService := NewUserService(s.db)

	userExists, err := userService.Exists(ctx, userID, accountGroupID)

	if err != nil {
		return err
	}

	// Checks if this is user's default account group.
	if userExists {
		// Sets user's own account group as default.
		if err := userService.RestoreAccountGroupByUserIDWithTx(ctx, tx, userID); err != nil {
			return err
		}

		// Deletes all tokens related to this account group to force a re-login.
		if err := NewUserActiveTokenService(s.db).DeleteByUserAndAccountGroupWithTx(ctx, tx, username, accountGroupID); err != nil {
			return err
		}
	}

	return tx.Commit()
}

// InsertWithTx inserts account group user with transaction.
func (s *accountGroupUserService) InsertWithTx(ctx context.Context, tx *sql.Tx, model *dbmodel.AccountGroupUser) error {
	model.ID = util.GenerateUUIDWithoutDashes()
	return s.accountGroupUserRepository.InsertWithTx(ctx, tx, model)
}

// DeleteByAccountGroupAndUserWithTx deletes user from account group by accountGroupID or userID.
func (s *accountGroupUserService) DeleteByAccountGroupAndUserWithTx(ctx context.Context, tx *sql.Tx, accountGroupID string, userID string) error {
	return s.accountGroupUserRepository.DeleteWithTx(ctx, tx, accountGroupID, userID)
}

// Container of AccountGroupUserService functions.
type accountGroupUserService struct {
	db                         *sql.DB
	accountGroupUserRepository repository.AccountGroupUserRepository
}
